// Mesh Tools
// (c) jimon game studio

#include "jeMeshTools.h"
#include "ILogManager.h"

#include "ITimer.h"

namespace je
{
	namespace scene
	{
		//! Insert Into Buffer List And Lock Buffer
		void InsertIntoBufferListAndLock(jeList<video::IHardwareBuffer*> & List,video::IHardwareBuffer * Buffer)
		{
			for(jeList<video::IHardwareBuffer*>::Iterator Iter = List.begin();Iter!=List.end();Iter++)
				if((*Iter) == Buffer)
					return;
			Buffer->Lock(core::BLT_READ);
			List.push_back(Buffer);
		}

		//! Get Data From Layer By Index
		void * GetDataFromLayerByIndex(video::IHardwareBufferArray::LayerInfo & Layer,u32 Index)
		{
			return reinterpret_cast<c8*>(Layer.Buffer->GetPtr())+Layer.Stride*Index+Layer.Offset;
		}

		//! Get jeVector2df From Layer By Index
		jeVector2df GetjeVector2dfFromLayerByIndex(video::IHardwareBufferArray::LayerInfo & Layer,u32 Index)
		{
			return *reinterpret_cast<jeVector2df*>(GetDataFromLayerByIndex(Layer,Index));
		}

		//! Get jeVector3df From Layer By Index
		jeVector3df GetjeVector3dfFromLayerByIndex(video::IHardwareBufferArray::LayerInfo & Layer,u32 Index)
		{
			return *reinterpret_cast<jeVector3df*>(GetDataFromLayerByIndex(Layer,Index));
		}

		//! Get Integer From Layer By Index
		u32 GetIntegerFromLayerByIndex(video::IHardwareBufferArray::LayerInfo & Layer,u32 Index)
		{
			u32 Value = 0;
			switch(Layer.VariableSize)
			{
			case 1:
				Value = *reinterpret_cast<u8*>(GetDataFromLayerByIndex(Layer,Index));
				break;
			case 2:
				Value = *reinterpret_cast<u16*>(GetDataFromLayerByIndex(Layer,Index));
				break;
			case 4:
				Value = *reinterpret_cast<u32*>(GetDataFromLayerByIndex(Layer,Index));
				break;
			default:
				JELOG_E("Unsupported index variable size")
				break;
			}
			return Value;
		}

		//! Set Data To HardwareBuffer By Index
		void SetDataToHardwareBufferByIndex(video::IHardwareBuffer * Buffer,u32 Index,u32 Offset,f32 Data)
		{
			*(reinterpret_cast<f32*>(Buffer->GetPtr())+4*Index+Offset) = Data;
		}


		//! Create Tangent Space
		void JE_ENGINE_FUNCTION_CALL CreateTangentSpaceSurface(ISurface * Surface)
		{
			video::jeRenderOperation_Draw * RenderOperation = static_cast<video::jeRenderOperation_Draw*>(Surface->GetRenderOperation());

			jeList<video::IHardwareBuffer*> BufferList;

			video::IHardwareBufferArray::LayerInfo & PositionLayerInfo = RenderOperation->Array->GetLayerInfo(video::HBAL_POSITION);
			video::IHardwareBuffer * PositionBuffer = static_cast<video::IHardwareBuffer*>(PositionLayerInfo.Buffer);
			InsertIntoBufferListAndLock(BufferList,PositionBuffer);

			video::IHardwareBufferArray::LayerInfo & NormalLayerInfo = RenderOperation->Array->GetLayerInfo(video::HBAL_NORMAL);
			video::IHardwareBuffer * NormalBuffer = static_cast<video::IHardwareBuffer*>(NormalLayerInfo.Buffer);
			InsertIntoBufferListAndLock(BufferList,NormalBuffer);

			video::IHardwareBufferArray::LayerInfo & UVLayerInfo = RenderOperation->Array->GetLayerInfo(video::HBAL_UV);
			video::IHardwareBuffer * UVBuffer = static_cast<video::IHardwareBuffer*>(UVLayerInfo.Buffer);
			InsertIntoBufferListAndLock(BufferList,UVBuffer);

			video::IHardwareBufferArray::LayerInfo & IndexesLayerInfo = RenderOperation->Array->GetLayerInfo(video::HBAL_INDEXES);
			video::IHardwareBuffer * IndexesBuffer = static_cast<video::IHardwareBuffer*>(IndexesLayerInfo.Buffer);
			InsertIntoBufferListAndLock(BufferList,IndexesBuffer);

			u32 VertexCount = PositionBuffer->GetSize() / PositionLayerInfo.Stride;
			u32 IndexCount = IndexesBuffer->GetSize() / IndexesLayerInfo.Stride;

			video::IHardwareBuffer * TangentBuffer = Surface->GetRender()->CreateHardwareBuffer(video::G3DT_VERTEX);
			TangentBuffer->Lock(core::BLT_WRITE);
			TangentBuffer->Allocate(VertexCount*16);

			switch(Surface->GetRenderOperationType())
			{
			case video::ROT_DRAW_TRIANGLES:
				{
					if(IndexCount % 3)
						JELOG_E("Index mod 3 != 0")

					for(u32 CurrentIndex = 0;CurrentIndex < IndexCount;CurrentIndex+=3)
					{
						u32 Index[3];
						jeVector3df Position[3];
						jeVector3df Normal[3];
						jeVector2df UV[3];

						for(u8 i=0;i<3;i++)
						{
							Index[i] = GetIntegerFromLayerByIndex(IndexesLayerInfo,CurrentIndex+i);

							if(Index[i] >= VertexCount)
								JELOG_E("Index >= VertexCount")

							Position[i] = GetjeVector3dfFromLayerByIndex(PositionLayerInfo,Index[i]);
							Normal[i] = GetjeVector3dfFromLayerByIndex(NormalLayerInfo,Index[i]);
							UV[i] = GetjeVector2dfFromLayerByIndex(UVLayerInfo,Index[i]);
						}

						jeVector2df SUV = UV[1]-UV[0];
						jeVector2df TUV = UV[2]-UV[0];

						jeVector3df B1 = Position[1]-Position[0];
						jeVector3df B2 = Position[2]-Position[0];

						f32 R = 1.0f / (SUV.X * TUV.Y - TUV.X * SUV.Y);

						jeVector3df SDir(TUV.Y*B1.X-SUV.Y*B2.X,TUV.Y*B1.Y-SUV.Y*B2.Y,TUV.Y*B1.Z-SUV.Y*B2.Z);
						SDir = SDir * R;

						jeVector3df TDir(SUV.X*B2.X-TUV.X*B1.X,SUV.X*B2.Y-TUV.X*B1.Y,SUV.X*B2.Z-TUV.X*B1.Z);
						TDir = TDir * R;

						jeVector3df Tangent[3];
						f32 TangentW[3];

						for(u8 i=0;i<3;i++)
						{
							Tangent[i] = (SDir-Normal[i]*Normal[i].DotProduct(SDir)).Normalize();
							TangentW[i] = Normal[i].CrossProduct(SDir).DotProduct(TDir) < 0.0f ? -1.0f : 1.0f;
							SetDataToHardwareBufferByIndex(TangentBuffer,Index[i],0,Tangent[i].X);
							SetDataToHardwareBufferByIndex(TangentBuffer,Index[i],1,Tangent[i].Y);
							SetDataToHardwareBufferByIndex(TangentBuffer,Index[i],2,Tangent[i].Z);
							SetDataToHardwareBufferByIndex(TangentBuffer,Index[i],3,TangentW[i]);
						}
					}
					break;
				}
			case video::ROT_DRAW_TRIANGLE_STRIP:
				{
					break;
				}
			default:
				{
					JELOG_E("Unsupported Render Operation Type for Tangent Space Calculation")
					break;
				}
			}

			TangentBuffer->UnLock();
			RenderOperation->Array->AttachBuffer(TangentBuffer,video::HBAL_TANGENT,4,4);
			TangentBuffer->Drop();

			for(jeList<video::IHardwareBuffer*>::Iterator Iter = BufferList.begin();Iter!=BufferList.end();Iter++)
				(*Iter)->UnLock();
		}

		//! Create Tangent Space
		void JE_ENGINE_FUNCTION_CALL CreateTangentSpaceMesh(jeMesh * Mesh)
		{
			for(jeMesh::MeshListType::Iterator MeshIter = Mesh->GetMeshes().begin();MeshIter != Mesh->GetMeshes().end();MeshIter++)
				CreateTangentSpaceMesh(*MeshIter);

			for(jeMesh::SurfacesListType::Iterator SurfaceIter = Mesh->GetSurfaces().begin();SurfaceIter != Mesh->GetSurfaces().end();SurfaceIter++)
				CreateTangentSpaceSurface(*SurfaceIter);
		}

		//! Calculate insphere radius of surface
		f32 JE_ENGINE_FUNCTION_CALL CalculateInsphereRadiusSurface(ISurface * Surface)
		{
			video::jeRenderOperation_Draw * RenderOperation = static_cast<video::jeRenderOperation_Draw*>(Surface->GetRenderOperation());

			video::IHardwareBufferArray::LayerInfo & PositionLayerInfo = RenderOperation->Array->GetLayerInfo(video::HBAL_POSITION);
			video::IHardwareBuffer * PositionBuffer = static_cast<video::IHardwareBuffer*>(PositionLayerInfo.Buffer);
			
			u32 VertexCount = PositionBuffer->GetSize() / PositionLayerInfo.Stride;

			PositionBuffer->Lock(core::BLT_READ);

			f32 MaxDistQR = 0.0f;

			if(PositionLayerInfo.VariablesPerObject == 3 && PositionLayerInfo.VariableSize == 4)
				for(u32 i=0;i<VertexCount;i++)
				{
					jeVector3df PosPtr = *reinterpret_cast<jeVector3df*>(reinterpret_cast<c8*>(PositionBuffer->GetPtr())+i*PositionLayerInfo.Stride+PositionLayerInfo.Offset);
					f32 DistQR = PosPtr.GetDistanceQR();
					if(DistQR > MaxDistQR)
						MaxDistQR = DistQR;
				}
			else
				JELOG_E("Dont support position layer")

			PositionBuffer->UnLock();

			return jeSqr(MaxDistQR);
		}

		//! Calculate insphere radius of mesh
		f32 JE_ENGINE_FUNCTION_CALL CalculateInsphereRadiusMesh(jeMesh * Mesh)
		{
			f32 MaxDist = 0.0f;
			for(jeMesh::MeshListType::Iterator MeshIter = Mesh->GetMeshes().begin();MeshIter != Mesh->GetMeshes().end();MeshIter++)
			{
				f32 Dist = CalculateInsphereRadiusMesh(*MeshIter);
				if(Dist > MaxDist)
					MaxDist = Dist;
			}

			for(jeMesh::SurfacesListType::Iterator SurfaceIter = Mesh->GetSurfaces().begin();SurfaceIter != Mesh->GetSurfaces().end();SurfaceIter++)
			{
				f32 Dist = CalculateInsphereRadiusSurface(*SurfaceIter);
				if(Dist > MaxDist)
					MaxDist = Dist;
			}
			return MaxDist;
		}
	}
}
